package dy.app.component;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Protocol;
import redis.clients.jedis.util.SafeEncoder;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * redis 通用组件
 * Author: cKnight
 * Date: 2024/6/11
 */
@Component
public class RedisCacheComponent {
    @Resource
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key
     * @param value
     * @param timeout  存储时间长度
     * @param timeUnit 存储时间单位
     * @return
     */
    public void set(Object key, Object value, Integer timeout, TimeUnit timeUnit) {
        try {
            if (timeout != null && timeUnit != null) {
                redisTemplate.opsForValue().set(key, value, Long.valueOf(timeout), timeUnit);
            } else {
                redisTemplate.opsForValue().set(key, value);
            }
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key
     * @param value
     * @param timeout 存储时间长度
     * @return
     */
    public void set(Object key, Object value, Long timeout) {
        try {
            if (timeout != null) {
                redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MILLISECONDS);
            } else {
                redisTemplate.opsForValue().set(key, value);
            }
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 获得缓存的基本对象
     *
     * @param key
     * @return
     */
    public String get(Object key) {
        try {
            Object o = redisTemplate.opsForValue().get(key);
            if (Objects.nonNull(o)){
                return o.toString();
            }
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
        return null;
    }

    /**
     * 获得缓存的基本对象
     *
     * @param key
     * @return
     */
    public Object getObject(Object key) {
        try {
            return redisTemplate.opsForValue().get(key);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 删除基本对象缓存
     *
     * @param key
     */
    public boolean remove(Object key) {
        try {
            return redisTemplate.delete(key);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 模糊删除
     *
     * @param key
     */
    public void indistinctDelete(Object key) {
        try {
            redisTemplate.delete(redisTemplate.keys("*" + key + "*"));
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }


    /**
     * 模糊删除
     *
     * @param key
     */
    public void removeLike(Object key) {
        try {
            Set<Object> keys = redisTemplate.keys("*" + key + "*");
            if (keys.size() > 0) {
                for (Object redisKey : keys) {
                    redisTemplate.delete(redisKey);
                }
            }
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 模糊查询
     *
     * @param key
     * @return
     */
    public Map<Object, String> getLike(Object key) {
        try {
            Map<Object, String> map = new HashMap<>();
            Set<Object> keys = redisTemplate.keys("*" + key + "*");
            if (keys.size() > 0) {
                for (Object redisKey : keys) {
                    map.put(redisKey, get(redisKey));
                }
                return map;
            } else {
                return null;
            }
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }


    /**
     * 模糊删除key
     *
     * @param pattern
     */
    public void deleteCacheWithPattern(String pattern) {
        try {
            Set<Object> keys = redisTemplate.keys("*" + pattern + "*");
            redisTemplate.delete(keys);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 基于原子性操作setNX
     * <p>
     * 1、如果setNX返回为true，表明当前key已设置到缓存内
     * <p>
     * 2、如果setNX返回为false，表明缓存中已存在当前key
     *
     * @param key
     * @param value
     */
    public Boolean lock(final String key, final Object value, Integer time, TimeUnit unit) {
        try {
            boolean lock = redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                    byte[] serialize = serializer.serialize(String.valueOf(value));
                    Object obj = connection.execute("set", key.getBytes(),
                            serialize,
                            SafeEncoder.encode("NX"),
                            SafeEncoder.encode("EX"),
                            Protocol.toByteArray(unit.toSeconds(Long.valueOf(time))));
                    return obj != null;
                }
            });
            return lock;
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key
     * @param value
     * @return
     */
    public void setNoExpire(String key, String value) {
        try {
            redisTemplate.opsForValue().set(key, value);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 对应key是否存在缓存
     *
     * @param key
     * @return
     */
    public Boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 缓存失效剩余时间
     *
     * @param key
     */
    public Long getExpire(String key) {
        try {
            return redisTemplate.getExpire(key);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 缓存失效剩余时间
     *
     * @param key
     */
    public Long getExpire(String key, TimeUnit timeUnit) {
        try {
            return redisTemplate.getExpire(key, timeUnit);
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }

    /**
     * 获得set
     *
     * @param key
     * @return
     */
    public Set<String> getSet(String key) {
        try {
            Set<Object> members = redisTemplate.opsForSet().members(key);
            if (!CollectionUtils.isEmpty(members)) {
                return members.stream().map(String::valueOf).collect(Collectors.toSet());
            }
            return new HashSet<>();
        } finally {
            RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
        }
    }


    /**
     * 分布式锁 lua脚本编写 单一锁
     * @param key
     * @param time 以秒为单位
     * @return 1 拿锁成功  0拿锁失败
     */
    public Long getLockByLua(String key,Long time){
        //key1是key，ARGV[1]是time
//        String script = "" +
//                "if redis.call('EXISTS',KEYS[1])==0 then " +
//                "redis.call('expire',KEYS[1],ARGV[1]); " +
//                "return 1;" +
//                " else " +
//                "return 0; " +
//                "end;";
        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
                " else return 0; end;";

        Long result = redisTemplate.execute(new
                DefaultRedisScript<>(script, Long.class), Arrays.asList(key),1,time);
        return result;
    }

    /**
     * 可重入锁 同一个value可以同时拿锁
     * @param key
     * @param value
     * @param time 以秒为单位
     * @return 大于0 拿锁成功 等于0 拿锁失败
     */
    public Long getReentrantLockByLua(String key,String value,Long time){
        //key1是key，ARGV[1]是value,ARGV[2]是time
//        String script = "" +
//                "if redis.call('EXISTS',KEYS[1])==0 " +  //如果key不存在
//                "then redis.call('set',KEYS[1],ARGV[1]); " +
//                "redis.call('expire',KEYS[1],ARGV[2]); " +
//                "return 1;" +
//                " elseif redis.call('get',KEYS[1]) == ARGV[1] " + //key存在 value一致
//                "then return 2;" +
//                " else " +
//                "return 0; " + //否则 返回
//                "end;";

        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
                " elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
                " else return 0; end;";

        Long result = redisTemplate.execute(new
                DefaultRedisScript<>(script, Long.class), Arrays.asList(key), value,time);
        return result;
    }
}
